package edu.umd.clip.ling;

//import fig.basic.IntPair;
import edu.umd.clip.util.MapFactory;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;

/**
 * An abstract class for Label objects which store attributes in a Map. Also
 * implements HasWord, HasTag, HasCategory, and HasContext by storing the words, tags,
 * etc. under standardized keys in the Map.
 * <p/>
 * For convenience, this class also contains standardized keys for
 * storing many other possible label attributes, such as head words, ner tags, etc.
 *
 * @author grenager
 */
public abstract class AbstractMapLabel implements Label, Serializable {

  static final int initCapacity = 5;

  // THE STANDARD KEYS

  /**
   * The standard key for storing a value in the map, as a String.
   */
  public static final String VALUE_KEY = "value";

  /**
   * The standard key for storing a tag in the map.
   */
  public static final String TAG_KEY = "tag";

  /**
   * The standard key for storing a word in the map, as a String.
   */
  public static final String WORD_KEY = "word";

  /**
   * The standard key for storing a lemma in the map.
   */
  public static final String LEMMA_KEY = "lemma";

  /**
   * The standard key for storing a category in the map, as a String.
   */
  public static final String CATEGORY_KEY = "cat";

  /**
   * The standard key for storing a projected category in the map, as a String.
   * For any word (leaf node), the projected category is the syntactic category
   * of the maximal constituent headed by the word.  Used in SemanticGraph.
   */
  public static final String PROJ_CAT_KEY = "pcat";

  /**
   * The standard key for storing a head word in the map as a pointer to
   * another node.
   */
  public static final String HEAD_WORD_KEY = "hw";

  /**
   * The standard key for storing a head tag in the map as a pointer to
   * another node.
   */
  public static final String HEAD_TAG_KEY = "ht";

  /**
   * The standard key for storing an integer index in the map.
   */
  public static final String INDEX_KEY = "idx";

  /**
   * The standard key for a propbank label which is of type Argument
   */
  public static final String ARG_KEY = "arg";

  /**
   * Another key used for propbank - to signify core arg nodes or predicate nodes
   */
  public static final String MARKING_KEY = "mark";

  /**
   * The standard key for Semantic Head Word which is a String
   */
  public static final String SEMANTIC_HEAD_WORD_KEY = "shw";

  /**
   * The standard key for Semantic Head Word POS which is a String
   */
  public static final String SEMANTIC_HEAD_POS_KEY = "shp";

  /**
   * Probank key for the Verb sense given in the Propbank Annotation, should
   * only be in the verbnode
   */
  public static final String VERB_SENSE_KEY = "vs";

  /**
   * The standard key for storing category with functional tags.
   */
  public static final String CATEGORY_FUNCTIONAL_TAG_KEY = "cft";

  /**
   * the standard key for the NER label.
   */
  public static final String NER_KEY = "ner";

  /**
   * the standard key for the coref label.
   */
  public static final String COREF_KEY = "coref";

  /** The standard key for the "shape" of a word: a String representing
   *  the type of characters in a word, such as "Xx" for a capitalized word.
   *  See {@link edu.stanford.nlp.process.WordShapeClassifier} for functions
   *  for making shape strings.
   */
  public static final String SHAPE_KEY = "shape";

  /**
   * The Standard key for storing the left terminal number relative to the
   * root of the tree of the leftmost terminal dominated by the current node
   */
  public static final String LEFT_TERM_KEY = "LEFT_TERM";

  /**
   * The standard key for the parent which is a String
   */
  public static final String PARENT_KEY = "PARENT";

  /**
   * The standard key for span which is a String
   */
  public static final String SPAN_KEY = "SPAN";

  /**
   * the standard key for the String that comes before this word
   * (from the InvertiblePTBTokenizer)
   */
  public static final String BEFORE_KEY = "before";

  /**
   * the standard key for the String that comes after this word
   * (from the InvertiblePTBTokenizer)
   */
  public static final String AFTER_KEY = "after";


  /**
   * the standard key for the actual, unmangled, pre-PTB'd word
   * (from the InvertiblePTBTokenizer)
   */
  public static final String CURRENT_KEY = "current";

  /**
   * The standard key for the answer which is a String
   */
  public static final String ANSWER_KEY = "answer";

  /**
   * The standard key for gold answer which is a String
   */
  public static final String GOLDANSWER_KEY = "goldAnswer";

  /**
   * The standard key for the features which is a Collection
   */
  public static final String FEATURES_KEY = "features";

  /**
   * The standard key for the semantic interpretation
   */
  public static final String INTERPRETATION_KEY = "interpretation";

  /**
   * The standard key for the semantic role label
   */
  public static final String ROLE_KEY = "srl";

  /**
   * The standard key for the gazetteer information
   */
  public static final String GAZETTEER_KEY = "gazetteer";

  public static final String STEM_KEY = "stem";

  public static final String POLARITY_KEY = "polarity";

  /**
   * for Chinese: character level information, segmentation
   */
  public static final String CH_CHAR_KEY = "char";
  public static final String CH_ORIG_SEG_KEY = "orig_seg"; // the segmentation info existing in the original text
  public static final String CH_SEG_KEY = "seg"; // the segmentation information from the segmenter

  /** This key is at present only used in CraigslistDemo. */
  public static final String BEGIN_POSITION_KEY = "BEGIN_POS";

  /** The character offset of last character of token in source. */
  public static final String END_POSITION_KEY = "END_POS";



  /**
   * The Map which stores all of the attributes for this label, and
   * the label value itself.
   */
  protected Map map;

  /**
   * The MapFactory which will be used to make new Maps in this AbstractMapLabel.
   */
  protected MapFactory mapFactory;

  protected AbstractMapLabel() {
    this(null);
  }

  protected AbstractMapLabel(MapFactory mapFactory) {
    if (mapFactory==null) {
      this.mapFactory = new MapFactory.HashMapFactory();
    } else {
      this.mapFactory = mapFactory;
    }
    this.map = this.mapFactory.buildMap();
  }

  // DIRECT MAP FUNCTIONALITY

  /**
   * Return the <code>Map</code> contained in this label.
   *
   * @return the <code>Map</code> contained in this AbstractMapLabel
   */
  public Map map() {
    return map;
  }

// yikes!  [commented out by dramage 4/5/06]
//
//  /**
//   * Set the <code>Map</code> contained in this AbstractMapLabel to the
//   * supplied <code>Map</code>.
//   *
//   * @param map the new <code>Map</code> for this label
//   */
//  public void setMap(Map map) {
//    this.map = map;
//  }

  /**
   * Returns the value to which the map contained in this label
   * maps the specified key.  Returns <code>null</code> if the map
   * contains no mapping for this key.  (Analogous to {@link
   * Map#get <code>Map.get(Object key)</code>}.)
   *
   * @param key key whose associated value is to be returned.
   * @return the value to which the map contained in this label
   *         maps the specified key, or <code>null</code> if the map
   *         contains no mapping for this key.
   */
  public Object get(Object key) {
    Object v = map.get(key);
    if (v == null) {
      return "";
    }
    return v;
  }

  /**
   * Associates the specified value with the specified key in the
   * map contained in this label.  (Analogous to {@link Map#put
   * <code>Map.put(Object key, Object value)</code>}.)
   * If the map previously contained a mapping for this key, the
   * old value is replaced by the specified value.
   *
   * @param key   key with which the specified value is to be associated.
   * @param value value to be associated with the specified key.
   * @return previous value associated with specified key, or
   *         <code>null</code> if there was no mapping for key.
   */
  public Object put(Object key, Object value) {
    return map.put(key, value);
  }

  // LABEL METHODS

  /**
   * @return the value for the label
   */
  public String value() {
    return (String) map.get(VALUE_KEY);
  }

  /**
   * Set the value for the label.
   *
   * @param value the value for the label
   */
  public void setValue(final String value) {
    map.put(VALUE_KEY, value);
  }

  /**
   * Set value for the label from a String.
   *
   * @param str the string value for the label
   */
  public void setFromString(final String str) {
    setValue(str);
  }

  // HASCATEGORY METHODS

  /**
   * Return the category of the label (or <code>null</code> if
   * none), which is stored in the map under the key {@link
   * AbstractMapLabel#CATEGORY_KEY <code>CATEGORY_KEY</code>}.
   *
   * @return the category for the label
   */
  public String category() {
    Object cat = map.get(CATEGORY_KEY);
    if (cat != null && cat instanceof String) {
      return (String) cat;
    } else {
      return null;
    }
  }

  /**
   * Set the category for the label.
   *
   * @param category the category for the label
   */
  public void setCategory(final String category) {
    map.put(CATEGORY_KEY, category);
  }

  // HASWORD METHODS



  /**
   * Return the word of the label, stored in the map under the key <code>WORD_KEY</code>.
   *
   * @return The word for this label
   */
  public String word() {
    return (String) map.get(WORD_KEY);
  }

  /**
   * Set the word for the label.
   *
   * @param word the head word for the label
   */
  public void setWord(String word) {
    map.put(WORD_KEY, word);
  }

  /**
   * The span of this node as begin and end positions if it exists
   *
   * @return The span
   */
//  public IntPair span() {
//    return (IntPair) map.get(SPAN_KEY);
//  }

  public void setSpan(String span) {
    map.put(SPAN_KEY, span);
  }

  /**
   * Return the head word of the label (or <code>null</code> if
   * none), which is stored in the map under the key {@link
   * AbstractMapLabel#HEAD_WORD_KEY <code>HEAD_WORD_KEY</code>}.
   *
   * @return the head word for the label
   */
  public Object headWord() {
    return map.get(HEAD_WORD_KEY);
  }

  /**
   * Set a pointer to the head-word for the label.
   */
  public void setHeadWord(Object headWordPtr) {
    map.put(HEAD_WORD_KEY, headWordPtr);
    if (headWordPtr instanceof HasWord) {
      setWord(((HasWord) headWordPtr).word());
    } else if (headWordPtr instanceof Label) {
      setWord(((Label) headWordPtr).value());
    }
  }

  /**
   * Returns the semantic head of the phrase if it exists, and null otherwise
   */
  public String getSemanticWord() {
    Object word = map.get(SEMANTIC_HEAD_WORD_KEY);
    return word != null ? word.toString() : null;
  }

  /**
   * Set the semantic head of the phrase
   */
  public void setSemanticWord(final String hWord) {
    map.put(SEMANTIC_HEAD_WORD_KEY, hWord);
  }


  /**
   * Returns the semantic head pos of the phrase if it exists, and null otherwise
   */
  public String getSemanticTag() {
    Object word = map.get(SEMANTIC_HEAD_POS_KEY);
    return word != null ? word.toString() : null;
  }

  /**
   * Set the semantic head pos of the phrase
   */
  public void setSemanticTag(final String hTag) {
    map.put(SEMANTIC_HEAD_POS_KEY, hTag);
  }

  /**
   * Return the head tag of the label (or <code>null</code> if none),
   * which is stored in the map under the key {@link
   * AbstractMapLabel#TAG_KEY <code>TAG_KEY</code>}.
   *
   * @return the head tag for the label
   */
  public String tag() {
    Object tag = map.get(TAG_KEY);
    if (tag != null && tag instanceof String) {
      return (String) tag;
    } else {
      return null;
    }
  }

  /**
   * Set the head tag for the label by storing it in the map under
   * the key {@link AbstractMapLabel#HEAD_TAG_KEY
   * <code>HEAD_TAG_KEY</code>}.
   *
   * @param tag the head tag for the label
   */
  public void setTag(final String tag) {
    map.put(TAG_KEY, tag);
  }

  public Object headTag() {
    return map.get(HEAD_TAG_KEY);
  }

  /**
   * Set a pointer to the head-word for the label.
   */
  public void setHeadTag(Object headTagPtr) {
    map.put(HEAD_TAG_KEY, headTagPtr);
    if (headTagPtr instanceof HasTag) {
      setTag(((HasTag) headTagPtr).tag());
    } else if (headTagPtr instanceof Label) {
      setTag(((Label) headTagPtr).value());
    }
  }

  /**
   * Return the NER type of the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#NER_KEY <code>NER_KEY</code>}.
   *
   * @return The NER label of the word
   */
  public String ner() {
    Object ner = map.get(NER_KEY);
    return (String) ner;
  }

  /**
   * Set the NER label for the word, using
   * the key {@link AbstractMapLabel#NER_KEY
   * <code>NER_KEY</code>}.
   *
   * @param ner The String NER label of the word
   */
  public void setNER(String ner) {
    map.put(NER_KEY, ner);
  }

  /**
   * Return the shape attribute of the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#SHAPE_KEY <code>SHAPE_KEY</code>}.
   *
   * @return The shape of the word.
   */
  public String shape() {
    return (String) map.get(SHAPE_KEY);
  }

  /**
   * Set the shape property for the word, using
   * the key {@link AbstractMapLabel#SHAPE_KEY
   * <code>SHAPE_KEY</code>}.
   *
   * @param shape A String giving the "shape" of the word.
   */
  public void setShape(String shape) {
    map.put(SHAPE_KEY, shape);
  }

  /**
   * Return the index of the label (or -1 if none), which is stored in
   * the map under the key {@link AbstractMapLabel#INDEX_KEY
   * <code>INDEX_KEY</code>}.
   *
   * @return the index for the label
   */
  public int index() {
    Object index = map.get(INDEX_KEY);
    if (index != null && index instanceof Integer) {
      return ((Integer) index).intValue();
    } else {
      return -1;
    }
  }

  /**
   * Set the index for the label by storing it in the contained map
   * under the key {@link AbstractMapLabel#INDEX_KEY
   * <code>INDEX_KEY</code>}.
   *
   * WARNING: do NOT call this if the vertice is already in a SemanticGraph.
   * Doing so will disrupt the equality criteria for the map, and will throw off
   * routines that check to see if this vertice is in the SemanticGraph.
   */
  public void setIndex(int index) {
    map.put(INDEX_KEY, new Integer(index));
  }


  /**
   * Return the beginning character offset of the label (or -1 if none).
   * This is stored in
   * the map under the key {@link AbstractMapLabel#BEGIN_POSITION_KEY}
   * <code>INDEX_KEY</code>.
   *
   * @return the beginning position for the label
   */
  public int beginPosition() {
    Object index = map.get(BEGIN_POSITION_KEY);
    if (index != null && index instanceof Integer) {
      return ((Integer) index).intValue();
    } else {
      return -1;
    }
  }

  /**
   * Set the beginning character offset for the label
   * by storing it in the contained map
   * under the key {@link AbstractMapLabel#BEGIN_POSITION_KEY}
   * <code>INDEX_KEY</code>.  Setting this key to "-1" can be used to
   * indicate no valid value.
   *
   * @param beginPos The beginning position
   */
  public void setBeginPosition(int beginPos) {
    map.put(BEGIN_POSITION_KEY, new Integer(beginPos));
  }


  /**
   * Return the ending character offset of the label (or -1 if none).
   * This is stored in
   * the map under the key {@link AbstractMapLabel#END_POSITION_KEY}
   * <code>INDEX_KEY</code>.
   *
   * @return the end position for the label
   */
  public int endPosition() {
    Object index = map.get(END_POSITION_KEY);
    if (index != null && index instanceof Integer) {
      return ((Integer) index).intValue();
    } else {
      return -1;
    }
  }

  /**
   * Set the ending character offset for the label
   * by storing it in the contained map
   * under the key {@link AbstractMapLabel#END_POSITION_KEY}
   * <code>INDEX_KEY</code>.  Setting this key to "-1" can be used to
   * indicate no valid value.
   *
   * @param endPos The end position
   */
  public void setEndPosition(int endPos) {
    map.put(END_POSITION_KEY, new Integer(endPos));
  }


  // HAS CONTEXT METHODS

  /**
   * Return the String before the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#BEFORE_KEY <code>BEFORE_KEY</code>}.
   *
   * @return the String before the word
   */
  public String before() {
    Object before = map.get(BEFORE_KEY);
    if (before == null) {
      before = "";
    }
    return (String) before;
  }

  /**
   * Set the String before the word by storing it in the map under
   * the key {@link AbstractMapLabel#BEFORE_KEY <code>BEFORE_KEY</code>}.
   *
   * @param before the String before the word
   */
  public void setBefore(String before) {
    map.put(BEFORE_KEY, before);
  }

  /**
   * Prepend this String to the current before String
   *
   * @param before the String to be prepended
   */
  public void prependBefore(String before) {
    String oldBefore = before();
    if (oldBefore == null) {
      oldBefore = "";
    }
    setBefore(before + oldBefore);
  }

  /**
   * Return the String which is the unmangled word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#CURRENT_KEY <code>CURRENT_KEY</code>}.
   *
   * @return the unmangled word
   */
  public String current() {
    Object current = map.get(CURRENT_KEY);
    if (current == null) {
      current = "";
    }
    return (String) current;
  }

  /**
   * Set the String which is the unmangled word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#CURRENT_KEY <code>CURRENT_KEY</code>}.
   *
   * @param current the unmangled word
   */
  public void setCurrent(String current) {
    map.put(CURRENT_KEY, current);
  }

  /**
   * Return the String after the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#AFTER_KEY <code>AFTER_KEY</code>}.
   *
   * @return the String after the word
   */
  public String after() {
    Object after = map.get(AFTER_KEY);
    if (after == null) {
      after = "";
    }
    return (String) after;
  }

  /**
   * Set the String after the word by storing it in the map under
   * the key {@link AbstractMapLabel#AFTER_KEY
   * <code>AFTER_KEY</code>}.
   *
   * @param after The String after the word
   */
  public void setAfter(String after) {
    map.put(AFTER_KEY, after);
  }

  /**
   * Append this String to the current after String
   *
   * @param after The String to be prepended
   */
  public void appendAfter(String after) {
    String oldAfter = after();
    if (oldAfter == null) {
      oldAfter = "";
    }
    setAfter(oldAfter + after);
  }

  /**
   * convenience method for getting answer *
   */
  public String answer() {
    return (String) get(ANSWER_KEY);
  }

  /**
   * convenience method for setting answer *
   */
  public void setAnswer(String answer) {
    put(ANSWER_KEY, answer);
  }

  /**
   * convenience method for getting gold answer *
   */
  public String goldAnswer() {
    return (String) get(GOLDANSWER_KEY);
  }

  /**
   * convenience method for setting gold answer *
   */
  public void setGoldAnswer(String goldAnswer) {
    put(GOLDANSWER_KEY, goldAnswer);
  }

  public Collection getFeatures() {
    return (Collection) map.get(FEATURES_KEY);
  }

  public void setFeatures(Collection features) {
    map.put(FEATURES_KEY, features);
  }

  public Object interpretation() {
    return map.get(INTERPRETATION_KEY);
  }

  public void setInterpretation(Object interpretation) {
    map.put(INTERPRETATION_KEY, interpretation);
  }

  public String getLemma() {
    return (String) map.get(LEMMA_KEY);
  }

  public void setLemma(String lemma) {
    map.put(LEMMA_KEY, lemma);
  }

  public String getRole() {
    return (String) map.get(ROLE_KEY);
  }

  public void setRole(String role) {
    map.put(ROLE_KEY, role);
  }

  private static final long serialVersionUID = -980833749513621054L;

}
